#include <stdio.h>
#include <time.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#include "chemnitz_stream.h"
#include "chemnitz_pool.h"
#include "chemnitz_paths.h"

static const uint32_t LOCALHOST = 0x0100007F;


void cleanup (/*@unused@*/ int signal); 

static CHEMNITZ_STREAM master;
static CHEMNITZ_POOL pool;
uint8_t buffer [1024];
int main (void)
{
	pid_t pid; 
	uid_t uid;
	gid_t gid;

	CHEMNITZ_STREAM session;
	uint8_t master_key [16];
	uint8_t session_key [16];
	uint64_t iv;
	uint32_t i;
	uint8_t c;
	uint64_t initial_entropy;

	int sockfd, newsockfd, infile, outfile;
	int n, e;
	uint16_t port;
	socklen_t clilen;
	socklen_t srvlen;
	uint32_t request;
	struct sockaddr_in server_addr, client_addr;

	struct sigaction termaction;

#ifndef DEBUG
	// drop root privileges like a hot potato.
	uid = CHEMNITZ_UID;
	gid = CHEMNITZ_GID;
	e = setgid (gid);
	if (e != 0)
	{
		printf ("Couldn't set gid");
		return 7;
	}
	e = setuid (uid);
	if (e != 0)
	{
		printf ("Couldn't set uid");
		return 7;
	}

	// daemonize.
	pid = fork ();
	if (pid)
	{
		exit (0);
	}
#endif

	// it's okay if children die. they probably deserved it.
	signal (SIGCHLD, SIG_IGN);

	// we want to trap SIGTERM to save state.
	termaction.sa_handler = cleanup;
	sigaction (SIGTERM, &termaction, 0);

	// save the lengths of these structures for subsequent calls.
	srvlen = (socklen_t) sizeof (server_addr);
	clilen = (socklen_t) sizeof (client_addr);

	sockfd = socket (AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0)
	{
		return 4;
	}

	// set up the server address structure...
	memset (&server_addr, 0, sizeof (server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = 0;
	server_addr.sin_addr.s_addr = INADDR_ANY;

	// and bind that address to a socket.
	e = bind (sockfd, (struct sockaddr *) &server_addr, sizeof (server_addr));
	if (e < 0)
	{
		return 4;
	}

	// finally, start listening on the socket we bound earlier.
	listen (sockfd, 5);
#ifdef DEBUG
	printf ("Listening...\n");
#endif
	// the network is all set up.

	// now we know what port we're listening on, 
	// so write it to a known place
	getsockname (sockfd, (struct sockaddr *) &server_addr, &srvlen);
	port = ntohs (server_addr.sin_port);
	outfile = open (SOCKET_PATH, O_WRONLY | O_CREAT, 0644);
	if (outfile < 0)
	{
		return 4;
	}
	e = write (outfile, (void *) &port, 2);
	close (outfile);
	
	// write our pid to a known place
	pid = getpid ();
	outfile = open (PID_PATH, O_WRONLY | O_CREAT, 0600); 
	if (outfile < 0)
	{
		return 4;
	}
	e = write (outfile, (void *) &pid, sizeof (pid));
	close (outfile);
	
	// and now we can do cryptography.

	// get the master key
	infile = open (KEY_PATH, O_RDONLY);
	if (infile < 0)
	{
		printf ("Please enter an initial master key. \n");
		printf ("You might roll a die several times, and type the numbers that come up. \n");
		printf ("A fair D6 should provide about 2.5 bits of entropy per roll. \n");
		printf ("Ctrl+D or EOF when done. \n");
		for (i = 0; read (0, &c, 1) == 1; i = (i + 1) & 0xF)
		{
			((uint8_t *) &master_key) [i] += c;
		}
#ifdef DEBUG
		e = write (1, &master_key, 16);
		printf ("\n");
#endif
	}
	else
	{
		e = read (infile, &master_key, 16);
		close (infile);
	}

	pool = chemnitz_pool_open (master_key);
	e = chemnitz_pool_deserialize (pool, POOL_PATH);
	if (e < 0)
	{
		// get some initial entropy
		initial_entropy = 0;
		printf ("Please provide some initial entropy. \n");
		printf ("You might roll a die several times, and type the numbers that come up. \n");
		printf ("A fair D6 should provide about 2.5 bits of entropy per roll. \n");
		printf ("Ctrl+D or EOF when done. \n");
		for (i = 0; read (0, &c, 1) == 1; i = (i + 1) & 0x7)
		{
			((uint8_t *) &initial_entropy) [i] += c;
		}
#ifdef DEBUG
		printf ("%016llx \n", initial_entropy);
#endif
	}

	if (! chemnitz_pool_is_open (pool))
	{
		return 3;
	}
	chemnitz_pool_put (pool, initial_entropy);
	chemnitz_pool_put (pool, (uint64_t) time (0));

	iv = chemnitz_pool_get (pool);
	master = chemnitz_stream_open (master_key, iv);
	if (! chemnitz_stream_is_open (master))
	{
		chemnitz_pool_close (pool);
		return 1;
	}

	// cryptography is all set up. now we loop, accepting connections
	// and serving them pseudorandom data
	while (1)
	{
		newsockfd = accept (sockfd, (struct sockaddr *) &client_addr, &clilen);
#ifdef LOCAL
		if (client_addr.sin_addr.s_addr != LOCALHOST)
		{
			close (newsockfd);
			continue;
		}
#endif // LOCAL
		(void) chemnitz_stream_read (master, session_key, 16);
		iv = chemnitz_pool_get (pool);
		session = chemnitz_stream_open (session_key, iv); 
		pid = fork ();
		if (pid < 0)
		{
			return 5;
		}
		if (pid == 0)
		{
			close (sockfd);
			if (! chemnitz_stream_is_open (session))
			{
				chemnitz_pool_close (pool);
				chemnitz_stream_close (master);
				return 2;
			}
			e = read (newsockfd, (void *) &request, 4);
			request = ntohl (request);
#ifdef DEBUG
			printf ("%u \n", request);
#endif
			while (request != 0)
			{
				while (request > 1024)
				{
					n = chemnitz_stream_read (session, buffer, 1024);
					e = write (newsockfd, buffer, n);
					request = request - 1024;
				}
				n = chemnitz_stream_read (session, buffer, request);
				e = write (newsockfd, buffer, n);
				e = read (newsockfd, (void *) &request, 4);
				request = ntohl (request);
#ifdef DEBUG
				printf ("%u \n", request);
#endif
			}
			exit (0);
		}
		else
		{
			close (newsockfd);
		}
	}
	return 0;
}

void cleanup (int signal)
{
	int outfile;
	int e;
	(void) signal;
	// write a new master key
	outfile = open (KEY_PATH, O_WRONLY | O_CREAT, 0600);
	chemnitz_stream_read (master, buffer, 16);
	e = write (outfile, buffer, 16);
	close (outfile);

	chemnitz_stream_close (master);
	chemnitz_pool_serialize (pool, POOL_PATH);
	chemnitz_pool_close (pool);
	unlink (SOCKET_PATH);
	unlink (PID_PATH);
	exit (0);
}

